#include "messageshowarea.h"

#include <QScrollBar>
#include <QVBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QFont>
#include <QFontMetrics>
#include <QPainter>
#include <QPainterPath>
#include <QTimer>
#include <QGraphicsView>
#include <QGraphicsPixmapItem>
#include <QFileDialog>
#include <QMenu>

#include "mainwidget.h"
#include "userinfowidget.h"
#include "debug.h"
#include "toast.h"
#include "model/datacenter.h"
#include "soundrecorder.h"

using namespace model;

////////////////////////////////////////////////////////
/// 表示消息展示区
////////////////////////////////////////////////////////

MessageShowArea::MessageShowArea() {
    // 1. 初始化基本属性
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    // 设置滚动条的样式
    this->setWidgetResizable(true);
    this->verticalScrollBar()->setStyleSheet(
        "QScrollBar:vertical { width: 6px; background-color: rgb(234, 234, 234); }"
        "QScrollBar::handle:vertical { background-color: rgb(164, 164, 164); border-radius: 3px; }"   //    滚动条样式
        "QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }"               //    隐藏上下箭头
        );
    this->horizontalScrollBar()->setStyleSheet("QScrollBar:horizontal { height: 0px; }");
    this->setStyleSheet("QScrollArea { border: none; }");

    // 2. 创建 Container 这样的 widget，作为包含内部元素的容器
    container = new QWidget();
    this->setWidget(container);

    // 3. 给 container 添加布局管理器
    QVBoxLayout* layout = new QVBoxLayout();
    layout->setSpacing(0);
    layout->setContentsMargins(0, 0, 0, 0);
    container->setLayout(layout);

    // 添加 "构造测试数据" 逻辑
#if TEST_UI
    UserInfo userInfo;
    userInfo.userId = QString::number(1000);
    userInfo.nickname = "张三";
    userInfo.signature = "从今天开始认真敲代码";
    userInfo.avatar = QIcon(":/resource/image/defaultAvatar.png");
    userInfo.phone = "18912345678";
    Message message = Message::makeMessage(TEXT_TYPE, "", userInfo, QString("这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息这是一条测试消息").toUtf8(), "");
    this->addMessage(false, message);

    for (int i = 1; i <= 30; ++i) {
        UserInfo userInfo;
        userInfo.userId = QString::number(1000 + i);
        userInfo.nickname = "张三";
        userInfo.signature = "从今天开始认真敲代码";
        userInfo.avatar = QIcon(":/resource/image/defaultAvatar.png");
        userInfo.phone = "18912345678";
        Message message = Message::makeMessage(TEXT_TYPE, "", userInfo, (QString("这是一条测试消息") + QString::number(i)).toUtf8(), "");
        this->addMessage(true, message);
    }
#endif
}

void MessageShowArea::addMessage(bool isLeft, const Message &message)
{
    // 构造 MessageItem 添加到布局管理器中
    MessageItem* messageItem = MessageItem::makeMessageItem(isLeft, message);
    this->container->layout()->addWidget(messageItem);
}

void MessageShowArea::addFrontMessage(bool isLeft, const Message &message)
{
    MessageItem* messageItem = MessageItem::makeMessageItem(isLeft, message);
    QVBoxLayout* layout = dynamic_cast<QVBoxLayout*>(this->container->layout());
    layout->insertWidget(0, messageItem);
}

void MessageShowArea::clear()
{
    QLayout* layout = this->container->layout();
    for (int i = layout->count(); i >= 0; --i) {
        QLayoutItem* item = layout->takeAt(i);
        if (item != nullptr && item->widget() != nullptr) {
            delete item->widget();
        }
    }
}

void MessageShowArea::scrollToEnd()
{
    // 实现思路：
    // 拿到滚动区域中的滚动条(垂直滚动条)
    // 获取到滚动条的最大 -- 根据最大值设置滚动位置
    // 为了使滚动效果更佳，能够在界面绘制好之后进行滚动条的设置
    // 给这里的滚动操作，加上个延时

    QTimer* timer = new QTimer();
    connect(timer, &QTimer::timeout, this, [=]() {
        // 垂直滚动条的最大值
        int maxValue = this->verticalScrollBar()->maximum();
        // 设置垂直滚动条的滚动位置
        this->verticalScrollBar()->setValue(maxValue);

        timer->stop();
        timer->deleteLater();
    });
    timer->start(50);
}

////////////////////////////////////////////////////////
/// 表示一个消息元素
/// 这个里面要同时支持 文本消息，图片消息，文件消息，语音消息
////////////////////////////////////////////////////////

MessageItem::MessageItem(bool isLeft) : isLeft(isLeft)
{

}

MessageItem *MessageItem::makeMessageItem(bool isLeft, const Message &message)
{
    // 1. 创建对象和布局管理器
    MessageItem* messageItem = new MessageItem(isLeft);
    QGridLayout* layout = new QGridLayout();
    layout->setContentsMargins(20, 10, 20, 0);
    layout->setSpacing(10);
    // item 最低不能低于 100
    messageItem->setFixedHeight(100);
    messageItem->setLayout(layout);

    // 2. 创建头像
    QPushButton* avatarBtn = new QPushButton();
    avatarBtn->setFixedSize(36, 36);
    avatarBtn->setIconSize(QSize(36, 36));
    avatarBtn->setIcon(message.sender.avatar);
    avatarBtn->setStyleSheet("QPushButton { border: none; }");
    if (isLeft) {
        layout->addWidget(avatarBtn, 0, 0, 2, 1, Qt::AlignTop | Qt::AlignLeft);
    } else {
        layout->addWidget(avatarBtn, 0, 1, 2, 1, Qt::AlignTop | Qt::AlignLeft);
    }

    // 3. 创建名字和时间
    QLabel* nameLabel = new QLabel();
    nameLabel->setText(message.sender.nickname + " | " + message.time);
    nameLabel->setAlignment(Qt::AlignBottom);
    nameLabel->setStyleSheet("QLabel { font-size: 12px; color: rgb(178, 178, 178); }");
    if(isLeft) {
        layout->addWidget(nameLabel, 0, 1, Qt::AlignLeft);
    } else {
        layout->addWidget(nameLabel, 0, 0, Qt::AlignRight);
    }

    // 4. 创建消息体
    QWidget* contentWidget = nullptr;
    switch (message.messageType) {
    case TEXT_TYPE:
        contentWidget = makeTextMessageItem(isLeft, message.content);
        break;
    case IMAGE_TYPE:
        contentWidget = makeImageMessageItem(isLeft, message.fileId, message.content);
        break;
    case FILE_TYPE:
        contentWidget = makeFileMessageItem(isLeft, message.fileId, message.content, message.fileName);
        break;
    case SPEECH_TYPE:
        contentWidget = makeSpeechMessageItem(isLeft, message.fileId, message.content);
        break;
    default:
        LOG() << "错误的消息类型! messageType = " << message.messageType;
    }
    if(isLeft) {
        layout->addWidget(contentWidget, 1, 1);
    } else {
        layout->addWidget(contentWidget, 1, 0);
    }

    // 5. 连接信号槽，处理用户点击头像的操作
    connect(avatarBtn, &QPushButton::clicked, messageItem, [=]() {
        MainWidget* mainWidget = MainWidget::getInstance();
        UserInfoWidget* userInfoWidget = new UserInfoWidget(message.sender, mainWidget);
        userInfoWidget->show();
    });

    // 6. 当用户修改了昵称的时候，同步修改此处的用户昵称
    if (!isLeft) {
        // 只是针对右侧消息做下列操作
        DataCenter* dataCenter = DataCenter::getInstance();
        connect(dataCenter, &DataCenter::changeNicknameDone, messageItem, [=]() {
            nameLabel->setText(dataCenter->getMyself()->nickname + " | " + message.time);
        });

        connect(dataCenter, &DataCenter::changeAvatarDone, messageItem, [=]() {
            avatarBtn->setIcon(dataCenter->getMyself()->avatar);
        });
    }

    return messageItem;
}

QWidget *MessageItem::makeTextMessageItem(bool isLeft, const QString &text)
{
    MessageContentLabel* messageContentLabel = new MessageContentLabel(text, isLeft);
    return messageContentLabel;
}

QWidget *MessageItem::makeImageMessageItem(bool isLeft, const QString& fileId, const QByteArray& content)
{
    MessageImageLabel* messageImageLabel = new MessageImageLabel(fileId, content, isLeft);
    return messageImageLabel;
}

QWidget *MessageItem::makeFileMessageItem(bool isLeft, const QString& fileId, const QByteArray& content, const QString& fileName)
{
    MessageFileLabel* messageFileLabel = new MessageFileLabel(fileId, content, fileName, isLeft);
    return messageFileLabel;
}

QWidget *MessageItem::makeSpeechMessageItem(bool isLeft, const QString& fileId, const QByteArray& content)
{
    MessageSpeechLabel* messageSpeechLabel = new MessageSpeechLabel(content, fileId, isLeft);
    return messageSpeechLabel;
}

/////////////////////////////////////////////////////
/// 创建类表示 "文本消息" 正文部分
/////////////////////////////////////////////////////

MessageContentLabel::MessageContentLabel(const QString &text, bool isLeft)
    : isLeft(isLeft)
{
    // 设置一下 SizePolicy
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    QFont font;
    font.setFamily("微软雅黑");
    font.setPixelSize(16);

    this->label = new QLabel(this);
    this->label->setText(text);
    this->label->setFont(font);
    this->label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
    this->label->setWordWrap(true);     // 设置自动换行
    this->label->setStyleSheet("QLabel { padding: 0, 10px; line-height: 1.2; background-color: transparent;}");
    this->label->setTextInteractionFlags(Qt::TextSelectableByMouse);
}

// 这个函数会该控件被显示的时候自动调用显示到
void MessageContentLabel::paintEvent(QPaintEvent *event)
{
    (void)event;

    // 1. 获取到父元素的宽度 (messagItem 也就是消息展示区的宽度)
    QObject* object = this->parent();
    if (!object->isWidgetType()) {
        // 当前这个对象的父元素不是预期的 QWidget，此时不需要进行任何后续的绘制操作
        return ;
    }
    QWidget* parent = dynamic_cast<QWidget*>(object);
    int width = parent->width() * 0.6;

    // 2. 计算当前本文，如果是一行放置，需要多宽
    QFontMetrics metrics(this->label->font());      // 消除了字体的影响
    int totalWidth = metrics.horizontalAdvance(this->label->text());

    // 3. 计算出行数是多少  此处留出的 60% 这样的宽度，是包含了左右的 20px 的空隙的
    int rows = (totalWidth / (width - 40)) + 1;
    if (rows == 1) {
        // 行数只有一行
        width = totalWidth + 40;
    }

    // 4. 根据行数计算得到高度   20 表示上下各有 10px 的边距
    int height = rows * (this->label->font().pixelSize() * 1.2) + 20;

    // 5. 绘制圆角矩形和箭头
    QPainter painter(this);
    // 绘制不规则图形
    QPainterPath path;

    // 设置抗锯齿
    painter.setRenderHint(QPainter::Antialiasing);
    if (isLeft) {
        // 设置画笔和画刷
        painter.setPen(QPen(QColor(255, 255, 255)));
        painter.setBrush(QColor(255, 255, 255));

        // 画一个矩形，(10, 0) 是相对于父元素 QLabel 的
        painter.drawRoundedRect(10, 0, width, height, 10, 10);
        // 绘制箭头
        path.moveTo(10, 15);    // 移动光标
        path.lineTo(0, 20);     // 画到 (0, 20)
        path.lineTo(10, 25);
        path.closeSubpath();    // 绘制的线形成闭合的多边形，才能进行使用 Brush 填充颜色
        painter.drawPath(path); // 真正的绘制操作

        this->label->setGeometry(10, 0, width, height);
    } else {
        painter.setPen(QPen(QColor(137, 217, 97)));
        painter.setBrush(QColor(137, 217, 97));

        // 圆角矩形左侧边的横坐标位置
        int leftPos = this->width() - width - 10;   // 10 是用来容纳 箭头的 宽度
        // 圆角矩形右侧边的横坐标位置
        int rightPos = this->width() - 10;
        // 绘制圆角矩形
        painter.drawRoundedRect(leftPos, 0, width, height, 10, 10);
        // 绘制箭头
        path.moveTo(rightPos, 15);
        path.lineTo(rightPos + 10, 20);
        path.lineTo(rightPos, 25);
        path.closeSubpath();
        painter.drawPath(path);

        this->label->setGeometry(leftPos, 0, width, height);
    }

    // 6. 重新设置父元素的高度，确保父元素足够高 (MessageItem) 能够容纳下上述绘制的消息显示的区域
    // 注意高度要涵盖之前名字和时间的 label 的高度，以及留点冗余空间
    parent->setFixedHeight(height + 50);
}

/////////////////////////////////////////////////////
/// 创建类表示 "图片消息" 正文部分
/// 两种情况 fileId 为空，content 为图片内容，说明是客户端像服务器发送消息， 否则就是服务端给客户端推送消息（只包含id）
/////////////////////////////////////////////////////
MessageImageLabel::MessageImageLabel(const QString &fileId, const QByteArray &content, bool isLeft)
    : fileId(fileId), content(content), isLeft(isLeft)
{
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    imageBtn = new QPushButton(this);
    imageBtn->setStyleSheet("QPushButton { border: node; }");
    connect(imageBtn, &QPushButton::clicked, this, &MessageImageLabel::clickImage);

    DataCenter* dataCenter = DataCenter::getInstance();
    if (content.isEmpty()) {
        // 从服务器拿到内容
        connect(dataCenter, &DataCenter::getSingleFileDone, this, &MessageImageLabel::updateUI);
        dataCenter->getSingleFileAsync(fileId);
    }
}

void MessageImageLabel::updateUI(const QString& fileId, const QByteArray& content)
{
    // 这个信号槽针对每个 MessageImageLabel 都会去连接，所以要判断是否是当前的对象触发的
    if (this->fileId != fileId) {
        return ;
    }

    // 对上了，真正显示图片内容
    this->content = content;

    // 进行绘制图片到界面上的操作
    this->update();
}

// 真正进行绘制图片到界面上的操作
void MessageImageLabel::paintEvent(QPaintEvent *event)
{
    (void)event;
    // 1. 先拿到该元素的父元素，看父元素的宽度是多少
    //    此处显示的图片的上限，是父元素宽度的 60%
    QObject* object = this->parent();
    if (!object->isWidgetType()) {
        // 这个逻辑上是不会存在的
        return ;
    }
    QWidget* parent = dynamic_cast<QWidget*>(object);
    int width = parent->width() * 0.6;

    // 2. 加载二进制数据为图片对象
    QImage image;
    if (content.isEmpty()) {
        // 此时说明图片的响应数据还没回来，此处先拿一个默认图片顶替一下
        QByteArray tmpContent = loadFileToByteArray(":/resource/image/image.png");
        image.loadFromData(tmpContent);
    } else {
        // 此处的 load 操作 QImage 能够自动识别当前图片是啥类型 png jpg
        image.loadFromData(content);
    }

    // 3. 针对图片进行缩放
    int height = 0;
    if (image.width() > width) {
        // 发现图片更宽，就需要缩放一下
        // 等比例缩放
        height = ((double)image.height() / (double)image.width()) * (double)width;
    } else {
        // 不需要缩放
        width = image.width();
        height = image.height();
    }

    // pixmap 只是一个中间变量，QImage 不能直接转成 QIcon，需要 QPixmap 中转一下
    QPixmap pixmap = QPixmap::fromImage(image);
    imageBtn->setIcon(QIcon(pixmap));
    imageBtn->setFixedSize(width, height);
    imageBtn->setIconSize(QSize(width, height));

    // 4. 由于图片高度是计算算出来的，该元素的父对象的高度，能够容纳下当前的元素
    // 此处 + 50是为了容纳下上方的名字部分，同时留下一点冗余的空间
    parent->setFixedHeight(height + 50);

    // 5. 确定按钮所在的位置
    // 左侧消息，和右侧消息，要显示的位置是不同的
    if (isLeft) {
        imageBtn->setGeometry(10, 0, width, height);
    } else {
        // 此时 imageBtn 的父对象就是 this 了，不是 parent 了 ，那是爷爷
        int leftPos = this->width() - width - 10;
        imageBtn->setGeometry(leftPos, 0, width, height);
    }
}

void MessageImageLabel::clickImage()
{
    if (content.isEmpty())
        return;

    /* 1. 单例窗口 */
    static QGraphicsView *view = nullptr;
    static QGraphicsScene *scene = nullptr;
    static QGraphicsPixmapItem *item = nullptr;

    if (!view) {
        view = new QGraphicsView();
        view->setWindowTitle("查看图片");
        view->setWindowIcon(QIcon(":/resource/image/logo.png"));
        view->resize(800, 600);
        view->setDragMode(QGraphicsView::ScrollHandDrag);
        view->setRenderHint(QPainter::SmoothPixmapTransform);

        scene = new QGraphicsScene(view);
        view->setScene(scene);
        item = new QGraphicsPixmapItem();
        scene->addItem(item);

        /* 2. 百分比标签 */
        static QLabel *percent = new QLabel(view->viewport());
        percent->setStyleSheet("background:rgba(0,0,0,180);color:white;padding:4px;");
        percent->setAlignment(Qt::AlignCenter);
        percent->hide();
    }

    /* 3. 加载图片并复位 100 % */
    QPixmap pix;
    pix.loadFromData(content);
    item->setPixmap(pix);
    scene->setSceneRect(pix.rect());
    view->resetTransform();

    /* 4. 就地过滤器：滚轮+百分比 */
    struct Filter : QObject {
        QGraphicsView *v;
        QLabel *per;
        explicit Filter(QGraphicsView *view, QLabel *p, QObject *parent = nullptr)
            : QObject(parent), v(view), per(p) {}
        bool eventFilter(QObject *, QEvent *e) override {
            if (e->type() == QEvent::Wheel) {
                auto *w = static_cast<QWheelEvent*>(e);
                qreal factor = (w->angleDelta().y() > 0) ? 1.15 : 1.0 / 1.15;

                /* 5. 更新缩放比例 */
                static qreal scale = 1.0;
                scale *= factor;
                scale = qBound(0.1, scale, 10.0);
                v->resetTransform();
                v->scale(scale, scale);

                /* 6. 显示百分比 */
                per->setText(QString("%1%").arg(qRound(scale * 100)));
                per->adjustSize();
                per->move(v->viewport()->width() - per->width() - 10,
                          v->viewport()->height() - per->height() - 10);
                per->show();

                /* 7. 2 秒后隐藏 */
                QTimer::singleShot(2000, per, &QLabel::hide);
                return true;
            }
            return false;
        }
    };

    /* 8. 只安装一次过滤器 */
    static bool installed = false;
    if (!installed) {
        installed = true;
        QLabel *per = view->findChild<QLabel *>();
        view->viewport()->installEventFilter(new Filter(view, per, view));
    }

    view->show();
    view->raise();
    view->activateWindow();
}

bool MessageImageLabel::eventFilter(QObject *obj, QEvent *e)
{
    if (e->type() == QEvent::Wheel) {
        QWheelEvent *w = static_cast<QWheelEvent*>(e);
        QGraphicsView *view = qobject_cast<QGraphicsView*>(obj);
        if (!view) return false;

        double scale = view->viewport()->property("scale").toDouble();
        double delta = (w->angleDelta().y() > 0) ? 1.15 : 1.0 / 1.15;
        scale *= delta;
        scale = qBound(0.1, scale, 10.0);     // 限制 10 % ~ 1000 %

        view->viewport()->setProperty("scale", scale);
        view->resetTransform();
        view->scale(scale, scale);
        return true;
    }
    return QWidget::eventFilter(obj, e);
}

/////////////////////////////////////////////////////
/// 创建类表示 "文件消息" 正文部分
/////////////////////////////////////////////////////
MessageFileLabel::MessageFileLabel(const QString &fileId, const QByteArray &content, const QString &fileName, bool isLeft)
    : isLeft(isLeft), fileId(fileId), content(content), fileName(fileName)
{
    // 设置一下 SizePolicy
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    QFont font;
    font.setFamily("微软雅黑");
    font.setPixelSize(16);

    this->label = new QLabel(this);
    this->label->setText("[文件] " + fileName);
    this->label->setFont(font);
    this->label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
    this->label->setWordWrap(true);     // 设置自动换行
    this->label->setStyleSheet("QLabel { padding: 0, 10px; line-height: 1.2; background-color: transparent;}");

    // 针对文件消息，并且 content 为空的情况下，通过网络来加载数据
    if (this->content.isEmpty()) {
        DataCenter* dataCenter = DataCenter::getInstance();
        connect(dataCenter, &DataCenter::getSingleFileDone, this, &MessageFileLabel::updateUI);
        dataCenter->getSingleFileAsync(fileId);
    } else {
        // 是自己发的消息
        loadContentDone = true;
    }
}

void MessageFileLabel::updateUI(const QString& fileId, const QByteArray& fileContent)
{
    // 这个信号槽针对每个 MessageFileLabel 都会去连接，所以要判断是否是当前的对象触发的
    if (this->fileId != fileId) {
        return ;
    }

    // 对上了，真正显示图片内容
    this->content = fileContent;
    loadContentDone = true;

    // 进行绘制图片到界面上的操作
    this->update();
}

void MessageFileLabel::paintEvent(QPaintEvent *event)
{
    (void)event;

    // 1. 获取父元素的宽度 (messagItem 也就是消息展示区的宽度)
    QObject* object = this->parent();
    if (!object->isWidgetType()) {
        // 当前这个对象的父元素不是预期的 QWidget，此时不需要进行任何后续的绘制操作
        return;
    }
    QWidget* parent = dynamic_cast<QWidget*>(object);
    int width = parent->width() * 0.3;

    // 2. 加载文件图标
    QPixmap fileIcon(":/resource/image/file.png");
    fileIcon = fileIcon.scaled(24, 24, Qt::KeepAspectRatio, Qt::SmoothTransformation);

    // 3. 计算文本区域的宽度
    int availableTextWidth = width - 40 - fileIcon.width() - 10; // 减去左右边距、图标宽度和图标与文本间距

    // 4. 计算高度 (图标高度 + 上下边距)
    int rowHeight = this->label->font().pixelSize() * 1.2;
    int height = 2 * rowHeight + 30;  // 两行文本高度 + 上下边距

    // 5. 绘制消息气泡
    QPainter painter(this);
    QPainterPath path;
    painter.setRenderHint(QPainter::Antialiasing);

    if (isLeft) {
        // 左侧消息 (收到的消息)
        painter.setPen(QPen(QColor(255, 255, 255)));
        painter.setBrush(QColor(255, 255, 255));

        // 绘制圆角矩形
        painter.drawRoundedRect(10, 0, width, height, 10, 10);

        // 绘制箭头
        path.moveTo(10, 15);
        path.lineTo(0, 20);
        path.lineTo(10, 25);
        path.closeSubpath();
        painter.drawPath(path);

        // 绘制文件图标
        painter.drawPixmap(20, (height - fileIcon.height()) / 2, fileIcon);

        // 设置文本位置
        this->label->setGeometry(20 + fileIcon.width() + 10, 0, availableTextWidth, height);
    } else {
        // 右侧消息 (发送的消息)
        painter.setPen(QPen(QColor(137, 217, 97)));
        painter.setBrush(QColor(137, 217, 97));

        // 计算位置
        int leftPos = this->width() - width - 10;
        int rightPos = this->width() - 10;

        // 绘制圆角矩形
        painter.drawRoundedRect(leftPos, 0, width, height, 10, 10);

        // 绘制箭头
        path.moveTo(rightPos, 15);
        path.lineTo(rightPos + 10, 20);
        path.lineTo(rightPos, 25);
        path.closeSubpath();
        painter.drawPath(path);

        // 绘制文件图标
        painter.drawPixmap(leftPos + width - 20 - fileIcon.width(), (height - fileIcon.height()) / 2, fileIcon);

        // 设置文本位置 (需要考虑文本宽度以右对齐)
        int labelLeft = leftPos + 20;
        int labelWidth = availableTextWidth;
        this->label->setGeometry(labelLeft, 0, labelWidth, height);
    }

    // 6. 设置父元素的高度
    parent->setFixedHeight(height + 50);
}

void MessageFileLabel::mousePressEvent(QMouseEvent *event)
{
    // 实现鼠标点击之后，触发文件另存为
    if (event->button() == Qt::LeftButton) {
        // 左键按下
        if (!this->loadContentDone) {   // 防止空文件，使用单独的变量
            Toast::showMessage("数据传输中，请稍后保存!");
            return ;
        }
        saveAsFile();
    }
}

void MessageFileLabel::saveAsFile()
{
    // 弹出对话框，让用户选择路径
    QString filePath = QFileDialog::getSaveFileName(this, "另存为", QDir::homePath(), "*");
    if (filePath.isEmpty()) {
        LOG() << "用户取消了文件另存为";
        return ;
    }
    writeByteArrayToFile(filePath, this->content);
}


/////////////////////////////////////////////////////
/// 创建类表示 "语音消息" 正文部分
/////////////////////////////////////////////////////
MessageSpeechLabel::MessageSpeechLabel(const QByteArray &content, const QString &fileId, bool isLeft)
    : isLeft(isLeft), fileId(fileId), content(content)
{
    // 设置一下 SizePolicy
    this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

    QFont font;
    font.setFamily("微软雅黑");
    font.setPixelSize(16);

    this->label = new QLabel(this);
    this->label->setText("[语音]");
    this->label->setFont(font);
    this->label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
    this->label->setWordWrap(true);     // 设置自动换行
    this->label->setStyleSheet("QLabel { padding: 0, 10px; line-height: 1.2; background-color: transparent;}");

    // 针对文件消息，并且 content 为空的情况下，通过网络来加载数据
    if (this->content.isEmpty()) {
        DataCenter* dataCenter = DataCenter::getInstance();
        connect(dataCenter, &DataCenter::getSingleFileDone, this, &MessageSpeechLabel::updateUI);
        dataCenter->getSingleFileAsync(fileId);
    } else {
        // 是自己发的消息
        loadContentDone = true;
    }
}

void MessageSpeechLabel::updateUI(const QString &fileId, const QByteArray &speechContent)
{
    // 这个信号槽针对每个 MessageSpeechLabel 都会去连接，所以要判断是否是当前的对象触发的
    if (this->fileId != fileId) {
        return ;
    }

    // 对上了，真正显示图片内容
    this->content = speechContent;
    loadContentDone = true;

    // 进行绘制图片到界面上的操作
    this->update();
}

void MessageSpeechLabel::paintEvent(QPaintEvent *event)
{
    (void)event;

    // 1. 获取到父元素的宽度 (messagItem 也就是消息展示区的宽度)
    QObject* object = this->parent();
    if (!object->isWidgetType()) {
        // 当前这个对象的父元素不是预期的 QWidget，此时不需要进行任何后续的绘制操作
        return ;
    }
    QWidget* parent = dynamic_cast<QWidget*>(object);
    int width = parent->width() * 0.6;

    // 2. 计算当前本文，如果是一行放置，需要多宽
    QFontMetrics metrics(this->label->font());      // 消除了字体的影响
    int totalWidth = metrics.horizontalAdvance(this->label->text());

    // 3. 计算出行数是多少  此处留出的 60% 这样的宽度，是包含了左右的 20px 的空隙的
    int rows = (totalWidth / (width - 40)) + 1;
    if (rows == 1) {
        // 行数只有一行
        width = totalWidth + 40;
    }

    // 4. 根据行数计算得到高度   20 表示上下各有 10px 的边距
    int height = rows * (this->label->font().pixelSize() * 1.2) + 20;

    // 5. 绘制圆角矩形和箭头
    QPainter painter(this);
    // 绘制不规则图形
    QPainterPath path;

    // 设置抗锯齿
    painter.setRenderHint(QPainter::Antialiasing);
    if (isLeft) {
        // 设置画笔和画刷
        painter.setPen(QPen(QColor(255, 255, 255)));
        painter.setBrush(QColor(255, 255, 255));

        // 画一个矩形，(10, 0) 是相对于父元素 QLabel 的
        painter.drawRoundedRect(10, 0, width, height, 10, 10);
        // 绘制箭头
        path.moveTo(10, 15);    // 移动光标
        path.lineTo(0, 20);     // 画到 (0, 20)
        path.lineTo(10, 25);
        path.closeSubpath();    // 绘制的线形成闭合的多边形，才能进行使用 Brush 填充颜色
        painter.drawPath(path); // 真正的绘制操作

        this->label->setGeometry(10, 0, width, height);
    } else {
        painter.setPen(QPen(QColor(137, 217, 97)));
        painter.setBrush(QColor(137, 217, 97));

        // 圆角矩形左侧边的横坐标位置
        int leftPos = this->width() - width - 10;   // 10 是用来容纳 箭头的 宽度
        // 圆角矩形右侧边的横坐标位置
        int rightPos = this->width() - 10;
        // 绘制圆角矩形
        painter.drawRoundedRect(leftPos, 0, width, height, 10, 10);
        // 绘制箭头
        path.moveTo(rightPos, 15);
        path.lineTo(rightPos + 10, 20);
        path.lineTo(rightPos, 25);
        path.closeSubpath();
        painter.drawPath(path);

        this->label->setGeometry(leftPos, 0, width, height);
    }

    // 6. 重新设置父元素的高度，确保父元素足够高 (MessageItem) 能够容纳下上述绘制的消息显示的区域
    // 注意高度要涵盖之前名字和时间的 label 的高度，以及留点冗余空间
    parent->setFixedHeight(height + 50);
}

void MessageSpeechLabel::mousePressEvent(QMouseEvent *event)
{
    // 实现鼠标点击之后，触发文件另存为
    if (event->button() == Qt::LeftButton) {
        // 左键按下
        if (!this->loadContentDone) {   // 防止空文件，使用单独的变量
            Toast::showMessage("数据传输中，请稍后保存!");
            return ;
        }
        SoundRecorder* soundRecorder = SoundRecorder::getInstance();
        this->label->setText("播放中...");
        connect(soundRecorder, &SoundRecorder::soundPlayDone, this, &MessageSpeechLabel::mousePressd, Qt::UniqueConnection);
        soundRecorder->startPlay(this->content);
    }
}

void MessageSpeechLabel::mousePressd()
{
    if (this->label->text() == "播放中...") {
        this->label->setText("[语音]");
    }
}

void MessageSpeechLabel::contextMenuEvent(QContextMenuEvent *event)
{
    LOG() << "触发上下文菜单";

    QMenu* menu = new QMenu(this);
    menu->setAttribute(Qt::WA_DeleteOnClose);
    QAction* action = menu->addAction("语音转文字");
    menu->setObjectName("speechMenu");
    connect(action, &QAction::triggered, this, [=]() {
        DataCenter* dataCenter = DataCenter::getInstance();
        connect(dataCenter, &DataCenter::speechConvertTextDone, this, &MessageSpeechLabel::speechConvertTextDone, Qt::UniqueConnection);
        dataCenter->speechConvertTextAsync(this->fileId, this->content);
    });

    QString menustyle =
        "QMenu#speechMenu{"
            "background:#ffffff;"
            "border:1px solid #ffffff;"   /* 1 px 足够盖住黑角 */
            "border-radius:6px;"          /* 圆角再小一点 */
            "padding:2px 0px;"            /* 上下更紧凑 */
            "margin:0px;"
            "font-family:'Segoe UI',Arial,sans-serif;"
        "}"
        "QMenu#speechMenu::item{"
            "padding:5px 20px 5px 28px;"  /* 左右、上下都收 2-4 px */
            "font-size:13px;"             /* 字再小一点 */
            "color:#1d1d1f;"
            "border-radius:3px;"
            "margin:0px 3px;"
        "}"
        "QMenu#speechMenu::item:selected{"
            "background:#e6f4ff;"
            "color:#0d47a1;"
        "}"
        "QMenu#speechMenu::separator{"
            "height:1px;"
            "background:#e5e5e5;"
            "margin:3px 8px;"             /* 分隔线也收紧 */
        "}";
    menu->setStyleSheet(menustyle);

    // 此处弹出一个 "模态对话框" 显示菜单、菜单项 会在用户进一步操作前，阻塞
    menu->exec(event->globalPos());
}

void MessageSpeechLabel::speechConvertTextDone(const QString &fileId, const QString &text)
{
    if (fileId != this->fileId) {
        // 此时的结果不是这条语音消息的结果
        return ;
    }
    // 修改界面内容
    this->label->setText("[语音转文字] " + text);
    this->update(); // 更新界面
}
